package com.jsjvm.client.classparser;

import java.util.HashMap;
import java.util.Vector;

import com.jsjvm.client.JSJVM;
import com.jsjvm.client.classparser.JAttribute.SourceFile;
import com.jsjvm.client.classparser.JMember.JField;
import com.jsjvm.client.classparser.JMember.JMethod;
import com.jsjvm.client.classparser.jconstants.JBaseConstant;
import com.jsjvm.client.classparser.jconstants.JClassConstant;
import com.jsjvm.client.classparser.jconstants.JMemberConstant;
import com.jsjvm.client.dbg.ObjectIDManager.SimpleIDIdentifiable;
import com.jsjvm.client.execution.JClassLoader;
import com.jsjvm.client.execution.JClassLoader.JClassNotLoadedException;
import com.jsjvm.client.util.DataInputStream;

/**
 * Parser for a .class file according to the specification found here:
 * 
 * http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
 * 
 * @author ctalau
 *
 */
public class JClass extends SimpleIDIdentifiable {
	private final static int MAGIC = 0xCAFEBABE;
	public enum Status {
		PREPARED,
		INITIALIZED
	}
	
	private Status status = Status.PREPARED;
	protected String name;
	private short flags;

	private JClass superClass;
	private String superClassName;
	
	protected JConstantPool cpool = null;
	private Vector<JClassConstant> interfaces = new Vector<JClassConstant>();

	private Vector<JField> fields = new Vector<JField>();
	private Vector<JMethod> methods = new Vector<JMethod>();
	private Vector<JAttribute> attributes = new Vector<JAttribute>();
	private JMethod clinit;
	
	private String sourceFile;
	private int minVer, majVer;
	
	/**
	 * Read the class file from the input stream and create an in-memory 
	 * representation of it. 
	 */
	public JClass(DataInputStream is) {
		try {
			int magic = is.readUInt(); 
			if (magic != MAGIC) 
				throw (new Exception("Corrupted class file: magic = " + magic));
			this.minVer = is.readUShort();
			this.majVer = is.readUShort();
			
			this.cpool =  new JConstantPool(is);
			
			this.flags = (short) is.readUShort();
			
			this.name = cpool.getClassName(is.readUShort());
			this.superClassName = cpool.getClassName(is.readUShort());
			
			int ifCnt = is.readUShort();
			for (int i = 0; i < ifCnt; i++){
				this.interfaces.add((JClassConstant)cpool.get(is.readUShort()));
			}
		
			int fieldCnt = is.readUShort();
			for (int i = 0; i < fieldCnt; i++){
				this.fields.add(new JField(is, this, i));
			}
			
			int mthCnt = is.readUShort();
			for (int i = 0; i < mthCnt; i++){
				JMethod jm = new JMethod(is, this, i);
				if (jm.memberName.equals(JMethod.CLINIT_METHOD_NAME)){
					clinit = jm;
				}
				this.methods.add(jm);
			}
			
			int attrCnt = is.readUShort();
			for (int i = 0; i < attrCnt; i++){
				JAttribute attr = JAttribute.readAttribute(is, cpool);
				this.attributes.add(attr);
				
				if (attr.getName().equals("SourceFile")){
					this.sourceFile = ((SourceFile)attr).getFileName();
				}
				
			}
		} catch (Exception e) {
			JSJVM.panic(e);
		}
	}

	private JClass(){		
	}
	
	/**
	 * Factory method to obtain the boot class
	 */
	public static JClass createBootClass(final String bootClassName){
		return new JClass(){{
			this.cpool = new JConstantPool(); 
			this.cpool.add(null);
			this.cpool.add(JMemberConstant.getBootMethodRef(bootClassName));
			this.cpool.add(new JClassConstant(JClassLoader.STRING_CLASS_NAME));
			this.getMethods().add(JMethod.getBootMehtod());			
		}};
	}
	
	/**
	 * Create array class instance
	 */
	private static HashMap <String, JClass> arrayClassCache = new HashMap<String, JClass>();
	public static JClass newArrayClass(final String type){
		JClass ret = arrayClassCache.get(type);
		if (ret == null){
			ret = new JClass(){{
				name = type;
			}};
			arrayClassCache.put(ret.name, ret);
		}
		return ret;
	}
		
	public void forceSuperClass() throws JClassNotLoadedException{
		if (this.superClass == null){
			if (this.superClassName == null)
				return;
			JClassLoader jcl = JClassLoader.getInstance();
			this.superClass = jcl.getClassByName(this.superClassName);
		}
	}
	
	public JClass getSuperClass() {
		return this.superClass;
	}
	
	@Override
	public String toString() {
		if (name.startsWith("["))
			return name;
		return "L" + this.name +";";
	}
	
	public JMethod getClinit() {
		return clinit;
	}
	
	public String getName() {
		return name;
	}
	public String getString(int i) {
		return cpool.getString(i);
	}
	
	public String getClassName(int i) {
		return cpool.getClassName(i);
	}
	
	public JClassConstant getClassConstant(int i) {
		return cpool.getClassConstant(i);
	}
	
	public JMemberConstant getMember(int i) {
		return cpool.getMember(i);
	}
	
	public JBaseConstant getBaseConstant(int i) {
		return cpool.getBaseConstant(i);
	}
	public Vector<JClassConstant> getInterfaces() {
		return interfaces;
	}
	
	public Vector<JField> getFields() {
		return fields;
	}
	
	public Vector<JMethod> getMethods() {
		return methods;
	}

	public Status getStatus() {
		return status;
	}
	
	public void setStatus(Status status) {
		this.status = status;
	}
	public int getMinorVersion() {
		return minVer;
	}
	public int getMajorVersion() {
		return majVer;
	}
	public short getFlags() {
		return flags;
	}
	public String getSourceFile() {
		return sourceFile;
	}
}

